package top.rtfsc8.web.wro4j;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.nio.file.FileSystems;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardWatchEventKinds;
import java.nio.file.WatchEvent;
import java.nio.file.WatchKey;
import java.nio.file.WatchService;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.servlet.ServletContext;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.web.context.ContextLoader;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.ServletContextResource;

/**<pre>
 * http://sunny-kaka.iteye.com/blog/1786564
 * http://meri-stuff.blogspot.sk/2012/08/wro4j-page-load-optimization-and-lessjs.html#Configuration
 * 
 * </pre>
 * @author Rtfsc8
 *
 */
public class Wro4jEL {

	private static Logger logger = LogManager.getLogger(Wro4jEL.class);

	private static ConcurrentMap<String, String> staticFileCache = new ConcurrentHashMap<String, String>();

	private static AtomicBoolean initialized = new AtomicBoolean(false);

	private static final String WRO_PATH = "/wro/";

	private static final String JS_SCRIPT = "<script type=\"text/javascript\" src=\"%s\"></script>";

	private static final String CSS_SCRIPT = "<link rel=\"stylesheet\" type=\"text/css\" href=\"%s\"/>";

	private static String contextPath = null;

	public static String jsFile(String filePath) {
		String fileUrl = "";
		
		try {
			init();
			String jsFile = staticFileCache.get(buildCacheKey(filePath, "js"));
			if (jsFile != null) {
				fileUrl = String.format(JS_SCRIPT, WRO_PATH + jsFile);
			}
		} catch (IOException e) {
			logger.error("Loding CSS File Not Found In Cache With File[{}]", 
					e);
		}
		
		return fileUrl;
	}

	public static String cssFile(String filePath) {
		String fileUrl = "";
		
		try {
			init();
			String cssFile = staticFileCache.get(buildCacheKey(filePath, "css"));
			if (cssFile != null) {
				fileUrl = String.format(CSS_SCRIPT, WRO_PATH + cssFile);
			}
		} catch (IOException e) {
			logger.error("Loding CSS File Not Found In Cache With File[{}]", 
					e);
		}
		
		return fileUrl;
	}

	public static void init() throws IOException {
		if (initialized.compareAndSet(false, true)) {
			WebApplicationContext webApplicationContext = ContextLoader.getCurrentWebApplicationContext();
			if (webApplicationContext != null) {
				ServletContext sc = webApplicationContext.getServletContext();
				if (sc == null) {
					throw new IOException("Find Static Resource But Servlet Context Is NULL");
				}
				contextPath = sc.getContextPath();
				File wroDirectory = new ServletContextResource(sc, WRO_PATH).getFile();
				if (!wroDirectory.exists() || !wroDirectory.isDirectory()) {
					throw new FileNotFoundException("Directory Not Exist When Finding Static Resource: " 
							+ wroDirectory.getAbsolutePath());
				}
				for (File file : wroDirectory.listFiles()) {
					handleNewFile(file);
				}
				logger.info("************************************************************************");
				new Thread(new WroFileWatcher(wroDirectory.getAbsolutePath())).start();
			}
		}
	}

	private static void handleNewFile(File file) {
		String fileName = file.getName();
		Pattern p = Pattern.compile("^^(\\w+)\\-\\d+\\.(js|css)$");
		Matcher m = p.matcher(fileName);
		if (!m.find() || m.groupCount() < 2) {
			return ;
		}
		String fakeName = m.group(1);
		String fileType = m.group(2);
		/* 暂时限定智能匹配 WRO_PATH 目录下的文件 */
		String key = buildCacheKey(WRO_PATH + fakeName, fileType);
		if (staticFileCache.putIfAbsent(key, fileName) != null) {
			synchronized (staticFileCache) {
				String cachedFileName = staticFileCache.get(key);
				if (fileName.compareTo(cachedFileName) > 0) {
					staticFileCache.putIfAbsent(key, contextPath + WRO_PATH + fileName);
					logger.info("put: {}", staticFileCache.get(key));
				}
			}
		}
	}

	private static String buildCacheKey(String fakeName, String fileType) {
		return fakeName + "-" + fileType;
	}

	static class WroFileWatcher implements Runnable {

		private static Logger logger = LogManager.getLogger(WroFileWatcher.class);

		private String wroAbsolutePathStr;

		public WroFileWatcher(String wroAbsolutePathStr) {
			this.wroAbsolutePathStr = wroAbsolutePathStr;
		}

		@SuppressWarnings("unchecked")
		@Override
		public void run() {
			Path path = Paths.get(wroAbsolutePathStr);
			File wroDirectory = path.toFile();

			try {
				if (!wroDirectory.exists() || !wroDirectory.isDirectory()) {
					String message = "Directory Not Exist By WRO Watcher: " 
							+ wroAbsolutePathStr;
					logger.error(message);
					throw new FileNotFoundException(message);
				}
				logger.warn("Now Watching Directory: {}", wroAbsolutePathStr);

				WatchService watcher = FileSystems.getDefault().newWatchService();
				path.register(watcher, StandardWatchEventKinds.ENTRY_CREATE);
				
				while (true) {
					WatchKey key = watcher.take();
					for (WatchEvent<?> event : key.pollEvents()) {
						if (event.kind() == StandardWatchEventKinds.OVERFLOW) {
							continue;
						}
						WatchEvent<Path> e = (WatchEvent<Path>) event;
						Path filePath = e.context();
						handleNewFile(filePath.toFile());
					}
					if (! key.reset()) {
						break;
					}
				}
			} catch (IOException | InterruptedException e) {
				logger.error("Exception On Watching Directory [{}]: {}", 
						wroAbsolutePathStr, 
						e);
			}
			logger.warn("Stoped Watching Directory [{}]", wroAbsolutePathStr);
		}
		
	}
}
